Wskaźniki w VB
Zacznijmy od tego, czym są
wskaźniki. Wskaźnik to taki adres funkcji. Otóż każda procedura wykonywalna
Windows (taka mogąca utworzyć wątek) ma swój unikalny adres, tak samo jest w
przypadku zmiennych (tych umieszczonych w pamięci operacyjnej). Wbudowane
funkcje do obsługi wskaźników dla zmiennych to VarPtr, StrPtr lub ObjPtr
(zależnie od typu zmiennej). Co daje nam wskaźnik? Przykładowo, dzięki wskaźnikowi
tablicy możemy za pomocą kopiowania pamięci (CopyMemory) stworzyć kopię tej
tablicy w innej tablicy, i to lepiej i szybciej niż w przypadku przepisywania
wartości. Jeśli chodzi o wskaźniki do procedur, to pozwalają one utworzyć wątek,
czyli uruchomić daną procedurę.
Tak samo możemy przekazać funkcji zmienną poprzez wartość (ByVal) lub
poprzez wskaźnik (ByRef). Na czym polega różnica? Na tym, że w pierwszym
przypadku funkcja może operować na wartości, a w drugim na samej zmiennej (i
dokonywać w niej zmian). Np.:
Call JakasFunkcja(ByRef
Zmienna)
W powyższym przypadku jakaś funkcja może dokonywać zmian w Zmiennej, jeśli
ByRef byłby zastąpiony przez ByVal to sytuacja taka nie mogłaby mieć miejsca
Aby pobrać wskaźnik funkcji należy użyć operatora AddressOf. Niedawno w
artykule Karola Kuczmarskiego, pt. "Procedury i Funkcje" z VBM #12
wyczytałem, że nie można zapisać w VB wskaźnika do zmiennej. Do niedawna i
ja tak sądziłem, ale wymyśliłem, że zrobię to tak:
Otóż wskaźniki wywołane (pobrane) za pomocą operatora AddressOf nie mogą
być zapisane do zmiennej, a jedynie przekazane w postaci argumentu. Argumentu i
nie tylko funkcjom API, bo funkcje API są dla kodu programu takie same jak
funkcje wew. programu, ale o nieznanym adresie pamięci (tylko do momentu wywołania;
technika późnego wiązania). Co z tego wynika? Możemy zrobić funkcję, której
argument będzie wskaźnikiem (przekazanym przez operator AddressOf), a potem
funkcja ta zwróci nam ten wskaźnik! Funkcja taka, może mieć postać:
Function
GetAddress(AddressStatement
As Long) As Long
GetAddress = AddressStatement
End Function
Później wywołanie takiej funkcji
nie stanowi żadnego problemu, np:
MsgBox GetAddress(AddressOf
GetAddress)
Czyż to nie piękne? Cóż ja mówię (a raczej piszę), to wspaniałe. Nie
tylko to mam do przedstawienia w tym artykule. Jeszcze jedna rzecz: wywoływanie
funkcji API bez deklaracji!!! Lecz znów pojawia się problem, bo jak na
razie nie potrafię przekazywać wywoływanym za pomocą adresu funkcją (lub
Sub'om) parametrów. Poniżej przedstawiłem, jak możemy użyć Sub'a FatalExit.
To nieprzyjemna operacja (chodzi o FatalExit), bo powoduje ona pojawienie się
okienka, że program wykonał niedozwoloną operację i musi być zamknięty
(lub bez pojawienia się tego okienka, następuje zamknięcie). Deklaracje (moduł):
Declare Function LoadLibrary Lib
"kernel32" Alias
"LoadLibraryA" (ByVal
lpLibFileName As String) As Long
Declare Function GetProcAddress Lib
"kernel32" (ByVal
hModule As Long, ByVal
lpProcName As String) As Long
Declare Function CreateThread Lib
"kernel32"
(lpThreadAttributes As Any, ByVal
dwStackSize As Long, ByVal
lpStartAddress As Long,
lpParameter As Any, ByVal
dwCreationFlags As Long,
lpThreadID As Long) As Long
Declare Function TerminateThread Lib
"kernel32" (ByVal
hThread As Long, ByVal
dwExitCode As Long) As Long
Declare Function CloseHandle Lib
"kernel32" (ByVal
hObject As Long) As Long
Global hThread As Long,
hThreadID As Long
LoadLibary służy do załadowania biblioteki DLL, GetProcAddress pobiera z tej
biblioteki wskaźnik danej procedury. Następnie CreateThread tworzy nowy wątek
(czyli wywołuje daną procedurę) za pomocą adresu procedury, CloseHandle
zamyka uchwyt wątku, a TerminateThread zakańcza wątek (nawet jeśli ten się
nie zakończył).
Dalej piszemy w kodzie (np. Form_Load):
Dim
hCommand, hKernel32
As Long
hKernel32 = LoadLibrary("kernel32.dll")
hCommand = GetProcAddress(hKernel32, "FatalExit")
hThread = CreateThread(ByVal 0&, ByVal 0&, hCommand, 0, 0, hThreadID)
hThread = 0
CloseHandle hThread
Dalej piszemy w kodzie (Form_Unload):
If
hThread <> 0 Then TerminateThread
hThread, 0
A teraz uwaga!!! Lepiej skompilować
plik i dopiero go uruchomić, bo VB zostanie zamknięty!
Innego przykładu dać nie mogłem, nie przekazałbym mu parametru. FatalExit może
zmylić, bo wydaje nam się, że wystąpił błąd, a w rzeczywistości to wywołana
funkcja odczytuje z jakiego modułu było ostatnie wywołanie i przedstawia go
jako źródło błędu. Jaki jest dowód tego, że to nie błąd a wywołana
funkcja? Pomimo ukazania się okienka, aplikacja może kontynuować działanie
(można zamknąć, przesuwać oknem).
Jeśli ktoś z was ma jakiekolwiek pytania/uwagi lub wskazówki dot.
przekazywania parametrów to niech pisze.
Marcin Porębski ( Doogie )
marcin.porebski@interia.pl